package com.godyhm.example.userendport;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.util.Log;

class OfflineEngineRecorder implements Runnable {

	private static final String tag = "OfflineEngineRecorder";

	/** 录音频率8000hz */
	private static final int AUDIO_FREQUENCE = 8000;

	/** 16bit采样率 */
	private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

	/** 录音编码格式 */
	private static final int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;

	/** 默认录音长度为8s */
	private static final int AUDIO_RECORD_BUFFER_SIZE_IN_MILLIS = 8000;

	/** 边界值，用于处理缓冲区即将使用完毕的行为 */
	private static final int EACHLEN = 1024;

	/** short类型语音缓冲区长度 */
	private static final int MAX_AUDIO_DATA_LENGTH = AUDIO_FREQUENCE
			* AUDIO_RECORD_BUFFER_SIZE_IN_MILLIS / 1000;

	/** 语音缓冲区 */
	private short[] mAudioData = new short[MAX_AUDIO_DATA_LENGTH];

	/** 用户输入的语音长度 */
	private volatile long mAudioDataLength;

	/** 语音缓冲区写入计数器 */
	private volatile int mAudioDataWritePos;

	/** 语音缓冲区读取计数器 */
	private volatile int mAudioDataReadPos = 0;

	/** 当前音量值 */
	private volatile long mDbLevel;

	/** 当前录制状态 */
	private volatile boolean mRecording = false;

	// should quit current thread
	private volatile boolean mExit = false;

	private volatile boolean mSendExit = false;

	// click button to indicate speak finish
	private volatile boolean mSpeakFinish;

	// we start a runnable to decode
	private WriteRunnable mWriteRunnable;

	// the time start speak
	private volatile long mTimeSpeakStart;

	// the time end speak
	private volatile long mTimeSpeakEnd;

	private volatile boolean mIsDumpPcm;

	public OfflineEngineRecorder() {
		File f=new File(Environment.getExternalStorageDirectory()
				+ "/dump-baidu.pcm");
		if(f.exists()){
			f.delete();
		}
		mIsDumpPcm = true;
	}


	private void writeToBytes(short audio[], int bufferReadResult) {
		byte[] bytes = new byte[bufferReadResult * 2];
		ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer()
				.put(audio);
		FileOutputStream fos = null;
		String fileName = Environment.getExternalStorageDirectory()
				+ "/dump-baidu.pcm";
		try {
			fos = new FileOutputStream(fileName, true);
			fos.write(bytes, 0, bufferReadResult * 2);
			fos.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				fos.close();
			} catch (IOException iex) {
			}
		}
	}

	/** 通过设置录制状态来启动或者关闭录音 */
	public void setRecordingStatus(boolean recording) {
		Log.i(tag, "setRecordingStatus: " + recording);
		mRecording = recording;
	}

	/** 当前是否在录制状态 */
	public boolean isRecording() {
		return mRecording;
	}

	/** 语音录制主线程 */
	@Override
	public void run() {
		AudioRecord record = null;
		mAudioDataWritePos = 0;
		mSpeakFinish = false;
		mSendExit = false;
		mAudioDataLength = MAX_AUDIO_DATA_LENGTH;

		// start thread for decode in order to decode quickly
		mWriteRunnable = new WriteRunnable();
		Thread decodeThread = new Thread(mWriteRunnable);
		decodeThread.start();

		try {

			// set the buffer size according to different phone
			short[] buffer = new short[EACHLEN];

			int bufferSize;
			int minBbufferSize = AudioRecord.getMinBufferSize(AUDIO_FREQUENCE,
					AUDIO_CHANNEL, AUDIO_ENCODING);

			bufferSize = minBbufferSize * 4;

			record = new AudioRecord(MediaRecorder.AudioSource.MIC,
					AUDIO_FREQUENCE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSize);

			// try to start recording
			try {
                record.startRecording();
				mTimeSpeakStart = System.currentTimeMillis();
				mWriteRunnable.increaseTriggerCounter();
			} catch (Exception e) {
				return;
			}
			while (true) {
				int bufferReadResult = record.read(buffer, 0, buffer.length);
				if (!mRecording) {
					break;
				}
				Log.i(tag, "bufferReadResult:" + bufferReadResult);
				Log.i(tag, "mAudioDataWritePos:" + mAudioDataWritePos);
				synchronized (mAudioData) {
					// exceed the max length
					if (mAudioDataWritePos + bufferReadResult >= MAX_AUDIO_DATA_LENGTH) {
						mRecording = false;
						break;
					}
					for (int i = 0; i < bufferReadResult; i++) {
						mAudioData[mAudioDataWritePos + i] = buffer[i];
					}
					mAudioDataWritePos += bufferReadResult;
				}
			}

			long count = 0;
			synchronized (mAudioData) {
				// get recorder total buffer length
				mTimeSpeakEnd = System.currentTimeMillis();
				mAudioDataLength = AUDIO_FREQUENCE * AUDIO_ENCODING / 2
						* (mTimeSpeakEnd - mTimeSpeakStart) / 1000;
				mAudioDataLength = mAudioDataLength < MAX_AUDIO_DATA_LENGTH ? mAudioDataLength
						: MAX_AUDIO_DATA_LENGTH;

				Log.i(tag, "mAudioDataLength in short:" + mAudioDataLength);
				// if decode thread did not send speech end ,send it
				if (!mSpeakFinish) {
					mSpeakFinish = true;
				}
				// decode结束，或者手动点击结束
				if (mExit) {
					if (!mSendExit) {
						mSendExit = true;
					}
					mWriteRunnable.stopDecode();
					record.stop();
					return;
				}
				count = (mAudioDataLength - mAudioDataWritePos) / buffer.length;
			}

			while (count-- >= 0) {
				Log.i(tag, "Count to decode the rest buffer:" + count);
				if (mExit) {
					record.stop();
					mWriteRunnable.stopDecode();
					return;
				}
				int bufferReadResult = record.read(buffer, 0, buffer.length);
				synchronized (mAudioData) {
					if (mAudioDataWritePos + bufferReadResult >= MAX_AUDIO_DATA_LENGTH) {
						break;
					}
					for (int i = 0; i < bufferReadResult; i++) {
						mAudioData[mAudioDataWritePos + i] = buffer[i];
					}
					mAudioDataWritePos += bufferReadResult;
				}
			}
			record.stop();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			record.release();
		}
	}

	class WriteRunnable implements Runnable {
		private volatile int triggerCounter = 0;

		@Override
		public void run() {
			boolean flagNeedSleep;

			while (true) {
				flagNeedSleep = false;

				synchronized (this) {
					if (triggerCounter < 0) {
						break;
					} else if (triggerCounter == 0) {
						flagNeedSleep = true;
					}
				}

				if (flagNeedSleep) {
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				decode();
			}
		}

		public synchronized void increaseTriggerCounter() {
			triggerCounter++;
		}

		public synchronized void stopDecode() {
			triggerCounter = -1000;
		}
	}

	public void decode() {
		int length = 512;
		boolean flagNeedSleep = false;
		synchronized (mAudioData) {
			if (mAudioDataReadPos + length >= mAudioDataWritePos) {
				if (mAudioDataReadPos + 2 * EACHLEN >= MAX_AUDIO_DATA_LENGTH) {
					length = MAX_AUDIO_DATA_LENGTH - mAudioDataReadPos;
				} else {
					flagNeedSleep = true;
				}
			}
		}

		if (flagNeedSleep) {
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return;
		}

		short[] buffer = new short[length];
		synchronized (mAudioData) {
			Log.i(tag, "mAudioDataReadPos:" + mAudioDataReadPos);
			if (mAudioDataReadPos + length >= MAX_AUDIO_DATA_LENGTH) {
				mAudioDataReadPos = 0;
			}
			mAudioDataReadPos += length;
			for (int i = 0; i < length; i++) {
				buffer[i] = mAudioData[mAudioDataReadPos + i];
			}
		}
		computePower(buffer, length);
		if (mIsDumpPcm) {
			writeToBytes(buffer, length);
		}
		// if the decode data read post exceed the length of mAudioDataLength,
		// it must be the last decode
		boolean decodeShouldOver = false;
		synchronized (mAudioData) {
			if (mAudioDataReadPos + 2 * EACHLEN >= mAudioDataLength) {
				decodeShouldOver = true;
			}
		}


		if (decodeShouldOver == false){
			return;
		}

		mRecording = false;
		mWriteRunnable.stopDecode();

		synchronized (mAudioData) {
			if (!mSpeakFinish) {
				mSpeakFinish = true;
			}
			// 手动点击结束
			if (mExit) {
				if (!mSendExit) {
					mSendExit = true;
				}
				return;
			}
		}

		// decode结束，请求退出
		mExit = true;
	}

	public long getCurrentDBLevelMeter() {
		return mDbLevel;
	}

	// get the volumn
	private void computePower(short[] buf, int sizeInShort) {
		if (buf == null) {
			mDbLevel = 0;
			return;
		}
		int sampleRate = 2;
		int sampleSize = Math.min(sizeInShort / sampleRate, 512);
		if (sampleSize <= 0) {
			mDbLevel = 0;
			return;
		}
		long sum = 0;
		for (int i = 0; i < sampleSize; i++) {
			sum += Math.abs(buf[i * sampleRate]);
		}
		long average = (sum / sampleSize);
		mDbLevel = average;
	}

}
